/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.core.session;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 会话上下文持有者，可以处理不同类型的Session
 * @author wangyandong
 */
public class CafSessionContextHolder {
    private static final Log logger = LogFactory.getLog(CafSessionManager.class);
    private static List<CafSessionHolder> holders;
    private static final Lock locker = new ReentrantLock();
    private static boolean initialized = false;

    /**
     * 构造函数
     * @param repo -
     */
    public CafSessionContextHolder(SessionRepository repo){
        this.holders = this.loadSessionHolders();
    }

    /**
     * 获取当前的Session对象
     */
    public static CafSession getCurrentSession() {
        //首先尝试获取HttpSession
        CafSessionHolder sessionHolder = getMatchingSessionHolder(SessionType.web);
        if (sessionHolder != null && sessionHolder.getCurrentSession()==null) {
            //如果HttpSession为null，尝试从线程上下文获取
            sessionHolder = getMatchingSessionHolder(SessionType.backend);
        }
        return sessionHolder.getCurrentSession();
    }

    /**
     * 获取深复制后的CafSession
     * @return
     */
    public static CafSession getCopyedCurrentSession(){
        //首先尝试获取HttpSession
        CafSessionHolder sessionHolder = getMatchingSessionHolder(SessionType.web);
        if (sessionHolder != null && sessionHolder.getCurrentSession()==null) {
            //如果HttpSession为null，尝试从线程上下文获取
            sessionHolder = getMatchingSessionHolder(SessionType.backend);
        }
        return sessionHolder.getCopyedCurrentSession();
    }

    /**
     * 设置当前上下文Session
     * @param session
     */
    public static void setCurrentSession(CafSession session) {
        Assert.notNull(session, "session can not be null");

        CafSessionHolder sessionHolder = getMatchingSessionHolder(session.getSessionType());
        if (sessionHolder ==null) {
            throw new RuntimeException("session type is not supported");
        }
        sessionHolder.setCurrentSession(session);
    }

    /**
     * 只给当前线程设置session，不给SecurityContextHolder设置
     * @param session
     */
    public static void setCurrentThreadPoolSession(CafSession session){
        Assert.notNull(session, "session can not be null");

        CafSessionHolder sessionHolder = getMatchingSessionHolder(session.getSessionType());
        if (sessionHolder ==null) {
            throw new RuntimeException("session type is not supported");
        }
        sessionHolder.setCurrentThreadPoolSession(session);
    }

    /**
     * 清理当前session
     * 仅限在异步线程调用
     */
    public static void clearSession(){
        List<CafSessionHolder> holders = getSessionHolders();
        if(holders!=null && holders.size()>0) {
            holders.stream().forEach(sessionHolder-> sessionHolder.clear());
        }
    }

    /**
     * 只清理当前线程设置session，不清理SecurityContextHolder设置
     */
    public static void clearCurrentThreadPoolSession(){
        List<CafSessionHolder> holders = getSessionHolders();
        if(holders!=null && holders.size()>0) {
            holders.stream().forEach(sessionHolder-> sessionHolder.clearCurrentThreadPoolSession());
        }
    }

    /**
     * 获取Session持有者列表
     * @return
     */
    private static List<CafSessionHolder> getSessionHolders(){
        if(holders==null && initialized ==false)
        {
            try {
                locker.lock();
                holders = loadSessionHolders();
                initialized = true;
            }finally {
                locker.unlock();
            }
        }
        return holders;
    }

    private static List<CafSessionHolder> loadSessionHolders() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        List<String> holderNames = SpringFactoriesLoader.loadFactoryNames(CafSessionHolder.class, classLoader);

        List<CafSessionHolder> holders = new ArrayList<>();
        for (String creatorName : holderNames) {
            try {
                Constructor<?> constructor = ClassUtils.forName(creatorName, classLoader).getDeclaredConstructor();
                ReflectionUtils.makeAccessible(constructor);
                holders.add((CafSessionHolder) constructor.newInstance());
            }
            catch (Throwable ex) {
                logger.trace(LogMessage.format("Failed to load %s", creatorName), ex);
            }
        }
        return holders;
    }

    /**
     * 根据session类型查找构造器
     * @param sessionType
     * @return
     */
    private static CafSessionHolder getMatchingSessionHolder(SessionType sessionType){
        List<CafSessionHolder> holders = getSessionHolders();
        if(holders==null || holders.size()==0)
            return null;
        return holders.stream()
                .filter(holder -> holder.getMatchingSessionTypes().indexOf(sessionType) >=0 )
                .findAny().orElse(null);
    }
}
